package com.elasticcloudservice.predict;


import com.filetool.util.MyUtil;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

public class Predict_smooth {

    public static final int FLAVOR_TYPES = 15;
    public static final int ITERATORS = 3000;  //梯度下降最大迭代次数
    public static final double MAX_COST = 10E-20; //最大允许的损失函数值
    public static final double LEARNING_RATE = 0.01; //学习率

    public static String[] predictVm(String[] ecsContent, String[] inputContent) throws ParseException {

        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");

        //训练时间跨度
        String firstTrainDate = (ecsContent[0].split("\t"))[2];
        String lastTrainDate = (ecsContent[ecsContent.length - 1].split("\t"))[2];
        Long trainStart = df.parse(firstTrainDate).getTime();
        Long trainEnd = df.parse(lastTrainDate).getTime();
        int trainDays = (int) Math.ceil((trainEnd - trainStart) * 1.0 / 86400000) + 1;
        //预测时间跨度
        String firstPredictDate = inputContent[inputContent.length - 2];
        String lastPredictDate = inputContent[inputContent.length - 1];
        Long predictStart = df.parse(firstPredictDate).getTime();
        Long predictEnd = df.parse(lastPredictDate).getTime();
        int predictDays = (int) Math.ceil((predictEnd - predictStart) / 86400000);

        //获取输入文件中的虚拟机信息
        List<String> nameListFromInput = getNameListFromInput(inputContent);

        String[] predict = new String[nameListFromInput.size()]; //预测结果
        int[][] flavorNumByDay = MyUtil.getFlavorNumByDay(ecsContent); //各种虚拟机每天出现的数量

        //对数据做平滑处理
        smooth(flavorNumByDay);

        int windowNum = trainDays / predictDays; //滑窗数量
        //统计各虚拟机在每个窗口中的数量
        int[][] flavorNumInWindow = new int[windowNum][FLAVOR_TYPES];
        for (int i = 0; i < windowNum; i++) {
            for (int j = 0; j < FLAVOR_TYPES; j++) {
                int sum = 0;
                for (int k = (windowNum - i - 1) * predictDays + 1; k < flavorNumByDay.length - i * predictDays; k++) {
                    sum += flavorNumByDay[k][j];
                }
                flavorNumInWindow[windowNum - i - 1][j] = sum;
            }
        }
        flavorNumInWindow = MyUtil.getSumOfFlavor(flavorNumInWindow); //各种虚拟机出现次数的累加
//        MyUtil.printArray(flavorNumInWindow);

        //二次指数平滑法 预测结果
        int[] predictResultSmooth = predictResultByTwoSmooth(flavorNumInWindow);

        //三次指数平滑法 预测结果
//        int[] predictResultSmooth = predictResultByThreeSmooth(flavorNumInWindow);

        //线性回归法 预测结果
        double[][] weights = trainModel(flavorNumInWindow); //训练模型
        int[] predictResultLiner = predictResultLiner(flavorNumInWindow, weights);

        int i = 0;
        for (String vmName : nameListFromInput) {
            int result = predictResultSmooth[Integer.parseInt(vmName.substring(6)) - 1];
//            int diff = predictResultSmooth[i] - predictResultLiner[i];
//            System.out.println(diff);
//            if (diff >= 0){
//                result = predictResultLiner[Integer.parseInt(vmName.substring(6)) - 1];
//            }else{
//                result = predictResultSmooth[Integer.parseInt(vmName.substring(6)) - 1];
//            }
//            int result =(int)Math.floor(predictResultSmooth[Integer.parseInt(vmName.substring(6)) - 1]*0.5 + predictResultLiner[Integer.parseInt(vmName.substring(6)) - 1] * 0.5);
            predict[i++] = vmName + " " + result;
        }

        //根据预测结果，分配虚拟机到服务器
        return AllocateMine.allocate(inputContent, predict);
    }

    /**
     * @Author: Chy
     * @Param:
     * @Description: 平滑处理，用倒数第二大的值代替噪点 88+
     * @Date: 17:16 2018/3/19
     */
    private static void smooth(int[][] flavorNumByDay) {

        for (int j = 0; j < FLAVOR_TYPES; j++) {
            //对每种flavor的历史数据进行升序排序
            int[] sortedArray = new int[flavorNumByDay.length];
            for (int i = 0; i < sortedArray.length; i++) {
                sortedArray[i] = flavorNumByDay[i][j];
            }
            Arrays.sort(sortedArray);

            //若最大值大于1.7倍的次大值，则将最大值用次大值替代
            int maxVaule = sortedArray[sortedArray.length - 1];
            int secondMaxValue = sortedArray[sortedArray.length - 2];
            if (maxVaule >= 1.7 * secondMaxValue && secondMaxValue != 0) {
                for (int i = 0; i < flavorNumByDay.length; i++) {
                    if (flavorNumByDay[i][j] == maxVaule)
                        flavorNumByDay[i][j] = secondMaxValue;
                }
            }
        }

        for (int j = 0; j < FLAVOR_TYPES; j++) {
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < flavorNumByDay.length; i++) {
                list.add(flavorNumByDay[i][j]);
            }
            int frequence = 0;
            int maxFrequenceValue = 0;
            for (Integer i : list) {
                if (i != 0) {
                    if (Collections.frequency(list, i) > maxFrequenceValue) {
                        frequence = Collections.frequency(list, i);
                        maxFrequenceValue = i;
                    }
                }
            }

            int noZeroCount = 0;
            int noZeroSum = 0;
            double average = 0.0;
            for (int i = 0; i < flavorNumByDay.length; i++) {
                if (flavorNumByDay[i][j] != 0) {
                    noZeroCount++;
                    noZeroSum += flavorNumByDay[i][j];
                }
            }
            average = Math.ceil(noZeroSum * 1.0 / noZeroCount);
//            System.out.println("flavor" + (j + 1) + "的非零平均值：" + average + " 非零数量：" + noZeroCount);
            for (int i = 0; i < flavorNumByDay.length; i++) {
                if (flavorNumByDay[i][j] > average * 4.0) {
                    flavorNumByDay[i][j] = maxFrequenceValue;
                }
            }
        }
    }


    /**
     * @Author: Chy
     * @Param:
     * @Description: 预测最终结果 二次指数平滑
     * @Date: 16:28 2018/3/17
     */
    private static int[] predictResultByTwoSmooth(int[][] flavorNumInWindow) {

        int[] predictResult = new int[FLAVOR_TYPES];
        double factor = 0.9;
        for (int i = 0; i < FLAVOR_TYPES; i++) {

            int[] flavorData = new int[flavorNumInWindow.length]; //flavor的历史数据
            double initialSmoothVal = 0.0; //平滑系数初始值
            for (int j = 0; j < flavorNumInWindow.length; j++) {
                flavorData[j] = flavorNumInWindow[j][i];
                if (j < 3)
                    initialSmoothVal += flavorNumInWindow[j][i];
            }
            initialSmoothVal /= 3;

            //平滑系数
            double[][] smoothValue = new double[flavorNumInWindow.length + 1][2];
            smoothValue[0][0] = smoothValue[0][1] = initialSmoothVal;
            for (int j = 1; j < smoothValue.length; j++) {
                smoothValue[j][0] = factor * flavorData[j - 1] + (1 - factor) * smoothValue[j - 1][0];
                smoothValue[j][1] = factor * smoothValue[j][0] + (1 - factor) * smoothValue[j - 1][1];
            }

            //计算a、b
            double[] a = new double[smoothValue.length];
            double[] b = new double[smoothValue.length];
            double temp = factor / (1 - factor);
            for (int j = 0; j < smoothValue.length; j++) {
                a[j] = 2 * smoothValue[j][0] - smoothValue[j][1];
                b[j] = temp * (smoothValue[j][0] - smoothValue[j][1]);
            }

            //预测
//            double lastErr = (a[a.length - 2] + b[b.length - 2] - flavorNumInWindow[flavorNumInWindow.length - 1][i]) / flavorNumInWindow[flavorNumInWindow.length - 1][i];
//            System.out.println("误差率为：" + lastErr);
            predictResult[i] = (int) Math.floor(a[a.length - 1] + b[b.length - 1]) - flavorNumInWindow[flavorNumInWindow.length - 1][i];
        }

        return predictResult;
    }


    /**
     * @Author: Chy
     * @Param:
     * @Description: 预测最终结果 三次指数平滑
     * @Date: 16:28 2018/3/17
     */
    private static int[] predictResultByThreeSmooth(int[][] flavorNumInWindow) {

        int[] predictResult = new int[FLAVOR_TYPES];
        double factor = 0.9;
        for (int i = 0; i < FLAVOR_TYPES; i++) {

            int[] flavorData = new int[flavorNumInWindow.length]; //flavor的历史数据
            double initialSmoothVal = 0.0; //平滑系数初始值
            for (int j = 0; j < flavorNumInWindow.length; j++) {
                flavorData[j] = flavorNumInWindow[j][i];
                if (j < 3)
                    initialSmoothVal += flavorNumInWindow[j][i];
            }
            initialSmoothVal /= 3;

            //平滑系数
            double[][] smoothValue = new double[flavorNumInWindow.length + 1][3];
            smoothValue[0][0] = smoothValue[0][1] = smoothValue[0][2] = initialSmoothVal;
            for (int j = 1; j < smoothValue.length; j++) {
                smoothValue[j][0] = factor * flavorData[j - 1] + (1 - factor) * smoothValue[j - 1][0];
                smoothValue[j][1] = factor * smoothValue[j][0] + (1 - factor) * smoothValue[j - 1][1];
                smoothValue[j][2] = factor * smoothValue[j][1] + (1 - factor) * smoothValue[j - 1][2];
            }

            //计算a、b、c
            double[] a = new double[smoothValue.length];
            double[] b = new double[smoothValue.length];
            double[] c = new double[smoothValue.length];
            double temp1 = 0.5 * factor / (1 - factor) / (1 - factor);
            double temp2 = 0.5 * factor * factor / (1 - factor) / (1 - factor);
            for (int j = 0; j < smoothValue.length; j++) {
                a[j] = 3 * smoothValue[j][0] - 3 * smoothValue[j][1] + smoothValue[j][2];
                b[j] = temp1 * ((6 - 5 * factor) * smoothValue[j][0] - 2 * (5 - 4 * factor) * smoothValue[j][1] +
                        (4 - 3 * factor) * smoothValue[j][2]);
                c[j] = temp2 * (smoothValue[j][0] - 2 * smoothValue[j][1] + smoothValue[j][2]);
            }

            //预测
//            double lastErr = (a[a.length - 2] + b[b.length - 2] - flavorNumInWindow[flavorNumInWindow.length - 1][i]) / flavorNumInWindow[flavorNumInWindow.length - 1][i];
//            System.out.println("误差率为：" + lastErr);
            predictResult[i] = (int) Math.ceil(a[a.length - 1] + b[b.length - 1] + c[c.length - 1]) - flavorNumInWindow[flavorNumInWindow.length - 1][i];
        }

        return predictResult;
    }

    /**
     * @Author: Chy
     * @Param:
     * @Description: 预测最终结果 线性回归
     * @Date: 16:28 2018/3/17
     */
    private static int[] predictResultLiner(int[][] flavorNumInWindow, double[][] weights) {

        //归一化
        double[][] scaling = featureScaling(flavorNumInWindow);

        int[] predictResult = new int[FLAVOR_TYPES];
        //预测结果
        for (int j = 0; j < FLAVOR_TYPES; j++) {
            int predictNum = 0;
            double temp = 0.0;
            for (int i = 1; i < scaling.length; i++) {
                temp += scaling[i][j] * weights[j][i];
            }
            temp = weights[j][0] + temp;
            predictNum = (int) Math.ceil(temp * (flavorNumInWindow[flavorNumInWindow.length - 1][j] - flavorNumInWindow[0][j] + flavorNumInWindow[0][j]));
//            predictNum = predictNum < flavorNumInWindow[flavorNumInWindow.length - 1][j] ? flavorNumInWindow[flavorNumInWindow.length - 1][j] : predictNum;
            predictResult[j] = predictNum - flavorNumInWindow[flavorNumInWindow.length - 1][j];
        }

        return predictResult;
    }

    /**
     * @Author: Chy
     * @Param:
     * @Description: 训练模型 线性模型
     * @Date: 12:23 2018/3/16
     */
    private static double[][] trainModel(int[][] flavorNumInWindow) {

        //归一化
        double[][] scaling = featureScaling(flavorNumInWindow);

        double[][] weights = new double[FLAVOR_TYPES][flavorNumInWindow.length];

        for (int j = 0; j < FLAVOR_TYPES; j++) {  //遍历15次，获取15种flavor的权重
            double[] weightsForEveflavor = new double[scaling.length]; //每种flavor的权重
//            Random random = new Random();
//            for (int m = 0; m < weightsForEveflavor.length; m++) {
//                    weightsForEveflavor[m] = random.nextFloat();
//            }

            int count = 0;
            while (count++ < ITERATORS) {
                //计算预测值：线性回归
                double predictValue = 0;
                for (int i = 0; i < scaling.length - 1; i++) {
                    predictValue += scaling[i][j] * weightsForEveflavor[i + 1];
                }
                predictValue += weightsForEveflavor[0];

                double diff = predictValue - scaling[scaling.length - 1][j];
                double costFunction = 0.5 * diff * diff;

                //若损失函数已足够小，结束迭代
                if (Math.abs(costFunction) < MAX_COST) {
                    StringBuffer sb = new StringBuffer("j=" + j + " count=" + count + " diff:" + diff + " predictValue: " + predictValue +
                            " costFunction:" + costFunction);
                    System.out.println(sb.toString());
                    break;
                }

                //梯度下降法更新每个参数
                for (int k = 0; k < weightsForEveflavor.length; k++) {
                    if (k == 0) {
                        weightsForEveflavor[k] = weightsForEveflavor[k] - LEARNING_RATE * diff;
                    } else {
                        weightsForEveflavor[k] = weightsForEveflavor[k] - LEARNING_RATE * diff * scaling[k][j];
                    }
                }
            }
            for (int m = 0; m < weightsForEveflavor.length; m++) {
                weights[j][m] = weightsForEveflavor[m];
            }

        }
        return weights;
    }

    /**
     * @Author: Chy
     * @Param:
     * @Description: 计算一个数的指定次方
     * @Date: 20:41 2018/3/18
     */
    private static double multiplication(int count, int value) {
        double result = 1.0;
        for (int i = 0; i < count; i++) {
            result *= value;
        }
        return result;
    }

    /**
     * @Author: Chy
     * @Param:
     * @Description: 归一化
     * @Date: 19:35 2018/3/17
     */
    public static double[][] featureScaling(int[][] flavorNumInWindow) {
        double[][] scaling = new double[flavorNumInWindow.length][FLAVOR_TYPES];

        for (int j = 0; j < FLAVOR_TYPES; j++) {
            int min = flavorNumInWindow[0][j];
            int max = flavorNumInWindow[flavorNumInWindow.length - 1][j];

            for (int i = 0; i < flavorNumInWindow.length; i++) {
                scaling[i][j] = (flavorNumInWindow[i][j] - min) * 1.0 / (max - min);
            }
        }

        return scaling;
    }

    /**
     * @Author: Chy
     * @Param:
     * @Description: 获取输入文件中虚拟机的名称
     * @Date: 13:55 2018/3/10
     */
    private static List<String> getNameListFromInput(String[] inputContent) {
        List<String> nameList = new ArrayList<>();
        for (int i = 3; i < 3 + Integer.parseInt(inputContent[2]); i++) {
            nameList.add(inputContent[i].split(" ")[0]);
        }
        return nameList;
    }
}
